package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"kuangstudy-mall/srvs/order-srv/global"
	"kuangstudy-mall/srvs/order-srv/model"
	"kuangstudy-mall/srvs/order-srv/proto"
	"math/rand"
	"strconv"
	"time"
)

type OrderServer struct {
}

// 查询用户购物车列表
func (handler *OrderServer) FindCartListByUserId(ctx context.Context, req *proto.CartUserInfo) (*proto.CartGoodListResponse, error) {
	// 定义一个购物车的列表
	var shoppingCarts []model.ShoppingCart
	var response proto.CartGoodListResponse
	result := global.DB.Where(&model.ShoppingCart{UserId: req.UserId}).Find(&shoppingCarts)
	if result.Error != nil {
		return nil, result.Error
	}
	// 返回购物车的总数量
	response.Total = int32(result.RowsAffected)
	// 返回购物车信息
	for _, shopCart := range shoppingCarts {
		response.GoodsList = append(response.GoodsList, &proto.ShopcartItemsResponse{
			Id:      shopCart.ID,
			UserId:  shopCart.UserId,
			GoodsId: shopCart.GoodsId,
			Nums:    shopCart.Nums,
			Checked: shopCart.Checked,
		})
	}

	return &response, nil
}

// 添加购物车
func (handler *OrderServer) AddGoodsToShoppingCart(ctx context.Context, req *proto.CartItemRequest) (*proto.ShopcartItemsResponse, error) {
	var shopCart model.ShoppingCart
	if result := global.DB.Where(&model.ShoppingCart{GoodsId: req.GoodsId, UserId: req.UserId}).Find(&shopCart); result.RowsAffected > 0 {
		// 这里传递的req.nums=1或者-1
		shopCart.Nums += req.Nums
	} else {
		shopCart.UserId = req.UserId
		shopCart.GoodsId = req.GoodsId
		shopCart.Nums = req.Nums
		shopCart.Checked = req.Checked
	}
	//save 方法 有id就更新，每页就保存
	global.DB.Save(&shopCart)

	return &proto.ShopcartItemsResponse{Id: shopCart.ID}, nil
}

// 更新购物车的状态
func (handler *OrderServer) UpdateShoppingCartStatus(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	var shopCart model.ShoppingCart
	if result := global.DB.Where(&model.ShoppingCart{GoodsId: req.GoodsId, UserId: req.UserId}).Find(&shopCart); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "购物车每页此商品...")
	}
	shopCart.Checked = req.Checked
	if req.Nums > 0 {
		shopCart.Nums = req.Nums
	} else {
		shopCart.Nums = 1
	}
	global.DB.Save(&shopCart)
	return &emptypb.Empty{}, nil
}

// 删除多个
func (handler *OrderServer) DeleteShoppingCartItems(ctx context.Context, req *proto.CartItemsRequest) (*emptypb.Empty, error) {
	global.DB.Unscoped().Delete(&model.ShoppingCart{}, req.Ids)
	return &emptypb.Empty{}, nil
}

// 删除单个
func (handler *OrderServer) DeleteShoppingCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	global.DB.Unscoped().Delete(&model.ShoppingCart{}, req.Id)
	return &emptypb.Empty{}, nil
}

func CreateOrderSn(userId uint64) string {
	now := time.Now()
	rand.Seed(time.Now().UnixNano())
	orderSn := fmt.Sprintf("%d%d%d%d%d%d%d%d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Nanosecond(),
		userId, rand.Intn(10)+10,
	)
	return orderSn
}

// 订单列表
func (handler *OrderServer) FindGoodsOrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {
	var orderList []model.OrderInfo
	var response proto.OrderListResponse

	// 1： 求总数
	var total int64
	global.DB.Where(&model.OrderInfo{UserId: req.UserId}).Count(&total)
	response.Total = int32(total)
	// 2: 查询分页订单信息
	global.DB.Scopes(Paginate(int(req.PageNo), int(req.PageSize))).Where(&model.OrderInfo{UserId: req.UserId}).Find(&orderList)
	for _, order := range orderList {
		response.OrderList = append(response.OrderList, &proto.OrderInfoResponse{
			Id:          order.ID,
			UserId:      order.UserId,
			OrderNo:     order.OrderNo,
			PayType:     order.PayType,
			OrderStatus: order.OrderStatus,
			PayPrice:    order.OrderPrice,
			Address:     order.Address,
			Username:    order.SingerName,
			Mobile:      order.SingerPhone,
			AddTime:     order.CreateAt.Format("2006-01-02 15:04:05"),
			Post:        order.Post,
		})
	}

	return &response, nil
}

// 订单明细
func (handler *OrderServer) FindGoodsOrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	var response proto.OrderInfoDetailResponse
	if result := global.DB.Where(&model.OrderInfo{BaseModel: model.BaseModel{ID: req.Id}, UserId: req.UserId}).First(&order); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在!")
	}

	orderInfoResponse := proto.OrderInfoResponse{}
	orderInfoResponse.Id = order.ID
	orderInfoResponse.UserId = order.ID
	orderInfoResponse.OrderNo = order.OrderNo
	orderInfoResponse.PayType = order.PayType
	orderInfoResponse.OrderStatus = order.OrderStatus
	orderInfoResponse.PayPrice = order.OrderPrice
	orderInfoResponse.Address = order.Address
	orderInfoResponse.Username = order.SingerName
	orderInfoResponse.Mobile = order.SingerPhone
	orderInfoResponse.AddTime = order.CreateAt.Format("2006-01-02 15:04:05")
	orderInfoResponse.Post = order.Post
	response.OrderInfo = &orderInfoResponse

	// 查询订单明细
	var orderGoods []model.OrderGoodsInfo
	if result := global.DB.Where(&model.OrderGoodsInfo{OrderId: order.ID}).Find(&orderGoods); result.Error != nil {
		return nil, result.Error
	}
	for _, orderGoods := range orderGoods {
		response.GoodsList = append(response.GoodsList, &proto.OrderItemsResponse{
			Id:         orderGoods.ID,
			OrderId:    orderGoods.OrderId,
			GoodsId:    orderGoods.GoodsId,
			GoodsName:  orderGoods.GoodsName,
			GoodsImage: orderGoods.GoodsImage,
			GoodsPrice: orderGoods.GoodsPrice,
			Nums:       orderGoods.Nums,
		})
	}

	return &response, nil
}

// 订单状态
func (handler *OrderServer) UpdateGoodsOrderStatus(ctx context.Context, req *proto.OrderStatus) (*emptypb.Empty, error) {
	//先查询，再更新 实际上有两条sql执行， select 和 update语句
	if result := global.DB.Model(&model.OrderInfo{}).Where("order_no = ?", req.OrderNo).Update("order_status", req.OrderStatus); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}

type OrderListener struct {
	Ctx        context.Context
	Code       codes.Code
	Detail     string
	ID         uint64
	OrderPrice float32
}

func (orderListener *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	/**
	 下单：
		 1：查询购物车中用户选中的商品
		 2：把选中的商品进行价格汇总----访问商品服务---远程服务调用
		 3：库存服务扣减 ---访问库存服务---远程服务调用
		 4: 保存订单的基础信息表
		 5: 保存订单的明细表
		 6: 开启消息队列队形订单的定时归还和修改状态
		 本地事务--分布式事务

	*/

	parentSpan := opentracing.SpanFromContext(orderListener.Ctx)

	// 把消息的订单数据反序列化出来
	var orderInfo model.OrderInfo
	json.Unmarshal(msg.Body, &orderInfo)

	// 1：查询购物车中用户选中的商品
	var shoppingCarts []model.ShoppingCart
	var goodsIds []uint64
	shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
	if result := global.DB.Where(&model.ShoppingCart{UserId: orderInfo.UserId, Checked: true}).Find(&shoppingCarts); result.RowsAffected == 0 {
		orderListener.Code = codes.InvalidArgument
		orderListener.Detail = "购物车中没有结算的商品！"
		return primitive.RollbackMessageState
	}
	shopCartSpan.Finish()

	// 这个map是用于后续的计算，解决双重for循环问题
	shoppingCartsMap := map[uint64]int32{}
	var shoppingCartsIds []uint64
	for _, cart := range shoppingCarts {
		goodsIds = append(goodsIds, cart.GoodsId)
		shoppingCartsIds = append(shoppingCartsIds, cart.ID)
		shoppingCartsMap[cart.GoodsId] = cart.Nums
	}

	// 2：把选中的商品进行价格汇总----访问商品服务---远程服务调用
	batchGoodsInfo := proto.BatchGoodsInfo{Ids: goodsIds}
	queryGoodsSpan := opentracing.GlobalTracer().StartSpan("query_goods", opentracing.ChildOf(parentSpan.Context()))
	goodsListResponse, err := GetNacosGoodsClient().BatchGetGoodsList(context.Background(), &batchGoodsInfo)
	if err != nil {
		orderListener.Code = codes.Internal
		orderListener.Detail = "批量查询商品失败"
		return primitive.RollbackMessageState
	}
	queryGoodsSpan.Finish()

	// 获取结算的商品价格
	var totalPrice float32
	var goodsStocksInfos []*proto.GoodsStocksInfo
	var orderGoodsInfos []*model.OrderGoodsInfo
	for _, goods := range goodsListResponse.Data {
		// 计算支付总价
		totalPrice += goods.SalePrice * float32(shoppingCartsMap[goods.Id])
		// 扣减库存使用
		goodsStocksInfos = append(goodsStocksInfos, &proto.GoodsStocksInfo{
			GoodsId: goods.Id,
			Num:     shoppingCartsMap[goods.Id],
		})
		// 准备订单明细和商品的数据
		orderGoodsInfos = append(orderGoodsInfos, &model.OrderGoodsInfo{
			OrderId:    0,
			BaseModel:  model.BaseModel{Status: 1, IsDeleted: 0},
			GoodsId:    goods.Id,
			GoodsName:  goods.Name,
			GoodsPrice: goods.SalePrice,
			GoodsImage: goods.Images,
			GoodsDesc:  goods.Desc,
			Nums:       shoppingCartsMap[goods.Id],
		})
	}

	// 3：库存服务扣减 ---访问库存服务---远程服务调用
	var response proto.GoodsSellListRequest
	response.GoodsList = goodsStocksInfos
	response.OrderNo = orderInfo.OrderNo
	queryInvSpan := opentracing.GlobalTracer().StartSpan("query_inv", opentracing.ChildOf(parentSpan.Context()))
	if _, err := GetNacosGoodsStockClient().GoodsStocksSell(context.Background(), &response); err != nil {
		orderListener.Code = codes.ResourceExhausted
		orderListener.Detail = "扣减库存失败"
		return primitive.RollbackMessageState
	}
	queryInvSpan.Finish()

	// 本地事务解决订单失败问题
	tx := global.DB.Begin()
	// 4: 保存订单
	saveOrderSpan := opentracing.GlobalTracer().StartSpan("save_order", opentracing.ChildOf(parentSpan.Context()))
	if result := tx.Save(&orderInfo); result.RowsAffected == 0 {
		tx.Rollback()
		orderListener.Code = codes.Internal
		orderListener.Detail = "创建订单失败"
		return primitive.CommitMessageState
	}
	saveOrderSpan.Finish()

	// 注意这里要把数据补齐
	orderListener.ID = orderInfo.ID
	orderListener.OrderPrice = totalPrice
	// 订单和订单明细产生管理，就是绑定orderId
	for _, orderGoods := range orderGoodsInfos {
		orderGoods.OrderId = orderInfo.ID
	}

	// 5: 保存订单明细
	saveOrderGoodsSpan := opentracing.GlobalTracer().StartSpan("save_order_goods", opentracing.ChildOf(parentSpan.Context()))
	if result := tx.CreateInBatches(orderGoodsInfos, 100); result.RowsAffected == 0 {
		tx.Rollback()
		orderListener.Code = codes.Internal
		orderListener.Detail = "批量插入订单明细失败"
		return primitive.CommitMessageState
	}
	saveOrderGoodsSpan.Finish()

	deleteShopCartSpan := opentracing.GlobalTracer().StartSpan("delete_shopcart", opentracing.ChildOf(parentSpan.Context()))
	// 6: 删除购物车订单信息--选中的商品
	if result := tx.Unscoped().Delete(&model.ShoppingCart{}, shoppingCartsIds); result.RowsAffected == 0 {
		tx.Rollback()
		orderListener.Code = codes.Internal
		orderListener.Detail = "删除购物车记录失败"
		return primitive.CommitMessageState
	}
	deleteShopCartSpan.Finish()
	// 7: 发送超时订单
	// 1: 创建一个生产者
	p, err := rocketmq.NewProducer(
		producer.WithInstanceName(strconv.FormatInt(time.Now().UnixNano(), 10)),
		producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"121.199.66.66:9876"})),
		producer.WithRetry(2),
	)

	if err != nil {
		zap.S().Errorf("生成Producer失败，%s", err.Error())
		panic("生成Producer失败")
	}

	if err := p.Start(); err != nil {
		fmt.Println("启动生产者失败,%s", err.Error())
		panic("启动生产者失败")
	}

	topic := "order_delay_timeout"
	message := primitive.NewMessage(topic, msg.Body)
	message.WithDelayTimeLevel(16)
	_, err = p.SendSync(context.Background(), message)
	if err != nil {
		zap.S().Errorf("发送延时消息失败，%v \n", err)
		tx.Rollback()
		orderListener.Code = codes.Internal
		orderListener.Detail = "发送延时消息失败"
		return primitive.CommitMessageState
	}

	tx.Commit() // commit意味所有的业务执行是正常的，就直接执行超时订单归还的业务。
	orderListener.Code = codes.OK
	return primitive.RollbackMessageState // 这里为什么是rollback
}

func (orderListener *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	json.Unmarshal(msg.Body, &orderInfo)
	// 根据订单编号查询订单是否存在, 如果不存在也要进行
	if result := global.DB.Where(model.OrderInfo{OrderNo: orderInfo.OrderNo}).First(&orderInfo); result.RowsAffected == 0 {
		// 根据订单编号去查询，如果查询不到，就意味着你需要进行库存归还。
		return primitive.CommitMessageState
	}
	return primitive.RollbackMessageState // 这里为什么是rollback
}

// 创建订单
func (handler *OrderServer) CreateGoodsOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	orderListener := OrderListener{Ctx: ctx}
	// 1: 创建一个订单归还的生产者
	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithInstanceName(strconv.FormatInt(time.Now().UnixNano(), 10)),
		producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"121.199.66.66:9876"})),
		producer.WithRetry(2),
	)

	if err != nil {
		zap.S().Errorf("生成Producer失败，%s", err.Error())
		return nil, err
	}

	if err := p.Start(); err != nil {
		fmt.Println("启动生产者失败,%s", err.Error())
		return nil, err
	}

	// 确定消息结构和主题
	topic := "order_transcation_reback"
	// 消息元数据
	orderInfoMessage := model.OrderInfo{
		OrderNo:     CreateOrderSn(req.UserId),
		UserId:      req.UserId,
		Address:     req.Address,
		Post:        req.Post,
		SingerName:  req.Username,
		SingerPhone: req.Mobile,
	}
	// 这里就是订单是否触发归还的业务消息处理
	orderMessage, _ := json.Marshal(orderInfoMessage)
	_, err = p.SendMessageInTransaction(context.Background(), primitive.NewMessage(topic, orderMessage))
	if err != nil {
		fmt.Printf("发送消息失败了: %s\n", err)
		return nil, status.Error(codes.Internal, "发送订单消息失败!")
	}

	// 捕捉orderListener的事务方法ExecuteLocalTransaction的返回，
	if orderListener.Code != codes.OK {
		return nil, status.Error(orderListener.Code, orderListener.Detail)
	}

	// 7: 返回
	return &proto.OrderInfoResponse{
		Id:          orderListener.ID,
		PayPrice:    orderListener.OrderPrice,
		UserId:      req.UserId,
		OrderNo:     orderInfoMessage.OrderNo,
		PayType:     orderInfoMessage.PayType,
		OrderStatus: orderInfoMessage.OrderStatus,
		Username:    req.Username,
		Address:     req.Address,
		Mobile:      req.Mobile,
		Post:        req.Post,
		AddTime:     orderInfoMessage.PayTime.Format("2006-01-02 15:04:05"),
	}, nil
}

/*
超时订单监听
*/
func OrderDelayTimeout(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	//msgs = orderInfoMessage := model.OrderInfo{
	//	OrderNo:     CreateOrderSn(req.UserId),
	//	UserId:      req.UserId,
	//	Address:     req.Address,
	//	Post:        req.Post,
	//	SingerName:  req.Username,
	//	SingerPhone: req.Mobile,
	//}
	for _, msg := range msgs {
		var orderInfo model.OrderInfo
		json.Unmarshal(msg.Body, &orderInfo)

		fmt.Println("获取超时订单消息：", time.Now())
		// 判断订单的幂等性，根据订单编号查询订单是否存在，如果不存在就直接返回，因为没有该订单，（数据其实保存成功了，消息也有，这个人为在数据把这个订单删除不了。）
		var order model.OrderInfo
		if result := global.DB.Where(model.OrderInfo{OrderNo: orderInfo.OrderNo}).First(&order); result.RowsAffected == 0 {
			// 根据订单编号去查询，如果查询不到，就意味着你需要进行库存归还。
			return consumer.ConsumeSuccess, nil
		}
		// 开始判断这笔交易是否成功, 如果不等于“TRADE_SUCCESS”,就意味着订单超时了。没有支付
		if order.OrderStatus != "TRADE_SUCCESS" {
			// 关闭订单
			tx := global.DB.Begin()
			// 发送订单归还消息
			order.OrderStatus = "TRADE_CLOSED"
			// 执行更新订单状态未："TRADE_CLOSED"
			tx.Save(&order)

			// 开发发送订单归还的消息
			// 7: 发送超时订单
			// 1: 创建一个生产者
			p, err := rocketmq.NewProducer(
				producer.WithInstanceName(strconv.FormatInt(time.Now().UnixNano(), 10)),
				producer.WithNsResolver(primitive.NewPassthroughResolver([]string{"121.199.66.66:9876"})),
				producer.WithRetry(2),
			)

			if err != nil {
				zap.S().Errorf("生成Producer失败，%s", err.Error())
				panic("生成Producer失败")
			}

			if err := p.Start(); err != nil {
				fmt.Println("启动生产者失败,%s", err.Error())
				panic("启动生产者失败")
			}

			topic := "order_transcation_reback"
			_, err = p.SendSync(context.Background(), primitive.NewMessage(topic, msg.Body))
			if err != nil {
				tx.Rollback()
				fmt.Println("发送失败,%s", err.Error())
				return consumer.ConsumeRetryLater, nil
			}

			tx.Commit()
			return consumer.ConsumeSuccess, nil
		}
	}
	return consumer.ConsumeSuccess, nil
}
